package operator

import (
	"context"
	"database/sql"
	"fmt"
	"gitee.com/zhendliu/sorm/decimal"
	"gitee.com/zhendliu/sorm/dialect"
	"gitee.com/zhendliu/sorm/log"
	"gitee.com/zhendliu/sorm/parser"
	"github.com/pkg/errors"
	"reflect"
	"strconv"
	"strings"
	"time"
)

type Operator interface {
	Exec(ctx context.Context, db *sql.DB, dia dialect.Dialect, sqlInfo string, args []interface{}) (resultList sql.Result, err error)
	ExecTx(ctx context.Context, tx *sql.Tx, dia dialect.Dialect, sqlInfo string, args []interface{}) (resultList sql.Result, err error)
	Query(ctx context.Context, db *sql.DB, dia dialect.Dialect, sqlInfo string, args []interface{}, list interface{}, depth int) (err error)
	QueryRowContext(ctx context.Context, db *sql.DB, dia dialect.Dialect, sqlInfo string, args []interface{}) *sql.Row
	QueryPagination(ctx context.Context, db *sql.DB, dia dialect.Dialect, sqlInfo string, args []interface{}, limit, offset int, list interface{}) (err error)
}

type Opt struct {
}

func NewOpt() Operator {
	return &Opt{}
}

func (o *Opt) Exec(ctx context.Context, db *sql.DB, dia dialect.Dialect, sqlInfo string, args []interface{}) (resultList sql.Result, err error) {
	start := time.Now()
	sqlInfo = dia.ReplaceSqlVariable(sqlInfo, args)
	defer func() {
		elapsed := time.Since(start)
		if err != nil {
			log.ErrorCaller("[%s] %s  args:%+v  \n error %s\n", parser.RoundDuration(elapsed), sqlInfo, args, err)
		} else {
			rows, _ := resultList.RowsAffected()
			log.InfofCaller("[%s] [rows:%d] %s  args:%+v ", parser.RoundDuration(elapsed), rows, sqlInfo, args)
		}
	}()

	resultList, err = db.ExecContext(ctx, sqlInfo, args...)
	return
}

func (o *Opt) ExecTx(ctx context.Context, tx *sql.Tx, dia dialect.Dialect, sqlInfo string, args []interface{}) (resultList sql.Result, err error) {
	start := time.Now()
	sqlInfo = dia.ReplaceSqlVariable(sqlInfo, args)
	defer func() {
		elapsed := time.Since(start)

		if err != nil {
			log.ErrorCaller("[%s] %s  args:%+v  \n error %s\n", parser.RoundDuration(elapsed), sqlInfo, args, err)
		} else {
			rows, err := resultList.RowsAffected()
			if err != nil {
				fmt.Println("错误：%+v\n", err)
				return
			}
			log.InfofCaller("[%s] [rows:%d] %s  args:%+v ", parser.RoundDuration(elapsed), rows, sqlInfo, args)
		}
	}()

	return tx.ExecContext(ctx, sqlInfo, args...)
}

// 直接查询
func (o *Opt) Query(ctx context.Context, db *sql.DB, dia dialect.Dialect, sqlInfo string, args []interface{}, list interface{}, depth int) (err error) {
	cnt := 0
	start := time.Now()
	sqlInfo = dia.ReplaceSqlVariable(sqlInfo, args)
	defer func() {
		elapsed := time.Since(start)
		if depth == 1 {
			log.InfofCallerPage("[%s] [rows:%d] sql:%s  args:%+v ", parser.RoundDuration(elapsed), cnt, sqlInfo, args)
		} else {
			log.InfofCaller("[%s] [rows:%d] %s  args:%+v ", parser.RoundDuration(elapsed), cnt, sqlInfo, args)
		}
	}()

	rows, err := db.QueryContext(ctx, sqlInfo, args...)
	if err != nil {
		return errors.Wrap(err, "query error")
	}
	defer func() {
		_ = rows.Close()
	}()
	columnTypes, err := rows.ColumnTypes()
	if err != nil {
		return
	}
	listVOf := reflect.ValueOf(list).Elem()
	listStrType := GetListStructType(list)
	for rows.Next() {
		cnt++
		var values []interface{}
		for range columnTypes {
			values = append(values, new(interface{}))
		}
		err = rows.Scan(values...)
		if err != nil {
			return
		}

		item := make(map[string]interface{})
		for index, data := range values {
			item[columnTypes[index].Name()] = GetSqlValue(columnTypes[index], data)
		}
		listStrValue := reflect.New(listStrType)
		SetStructColumnValues(item, listStrValue.Elem(), dia)
		if reflect.ValueOf(list).Elem().Kind() == reflect.Struct {
			listVOf.Set(listStrValue.Elem())
			return
		} else {
			listVOf = reflect.Append(listVOf, listStrValue.Elem())
		}
	}
	reflect.ValueOf(list).Elem().Set(listVOf)
	return
}

func (o *Opt) QueryRowContext(ctx context.Context, db *sql.DB, dia dialect.Dialect, sqlInfo string, args []interface{}) *sql.Row {
	start := time.Now()
	defer func() {
		elapsed := time.Since(start)
		log.InfofCaller("[%s] - %s %+v", parser.RoundDuration(elapsed), sqlInfo, args)
	}()
	return db.QueryRowContext(ctx, dia.ReplaceSqlVariable(sqlInfo, args), args...)
}

// 通过limit offset查询
func (o *Opt) QueryPagination(ctx context.Context, db *sql.DB, dia dialect.Dialect, sqlInfo string, args []interface{}, limit, offset int, list interface{}) (err error) {
	if limit == 0 {
		limit = 10
	}

	pageSql := dia.PackLimitOffsetSql(sqlInfo, limit, offset)

	err = o.Query(ctx, db, dia, pageSql, args, list, 1)
	if err != nil {
		return err
	}
	return
}

func GetListStructType(list interface{}) reflect.Type {
	tOf := reflect.TypeOf(list)
	if tOf.Kind() == reflect.Slice {
		return tOf.Elem()
	}
	vOf := reflect.ValueOf(list)
	if vOf.Kind() == reflect.Ptr {
		return GetListStructType(vOf.Elem().Interface())
	}
	if tOf.Kind() == reflect.Ptr { //指针类型获取真正type需要调用Elem
		tOf = tOf.Elem()
	}
	return tOf
}

func GetSqlValue(columnType *sql.ColumnType, data interface{}) (value interface{}) {
	if data == nil {
		return
	}
	typeName := reflect.TypeOf(data).String()
	if typeName == "*dm.DmClob" {
		typeV := reflect.ValueOf(data)
		method := typeV.MethodByName("GetLength")
		vs := method.Call([]reflect.Value{})
		if vs[1].Interface() == nil {
			length := vs[0].Int()
			method = typeV.MethodByName("ReadString")
			vs = method.Call([]reflect.Value{reflect.ValueOf(1), reflect.ValueOf(int(length))})
			value = vs[0].String()
		}
		return
	} else if typeName == "*dm.DmBlob" {
		typeV := reflect.ValueOf(data)
		method := typeV.MethodByName("Read")
		var bs []byte
		var readBs = make([]byte, 1024*1024)
		for {
			vs := method.Call([]reflect.Value{reflect.ValueOf(readBs)})
			n := vs[0].Int()
			if n > 0 {
				bs = append(bs, readBs[0:n]...)
			}
			if vs[1].Interface() != nil {
				break
			}
		}

		value = string(bs)
		return
	} else if typeName == "godror.Number" {
		typeV := reflect.ValueOf(data)
		method := typeV.MethodByName("String")
		vs := method.Call([]reflect.Value{})
		value = vs[0].String()
		return
	}
	vOf := reflect.ValueOf(data)
	if vOf.Kind() == reflect.Ptr {
		if vOf.IsNil() {
			return nil
		}
		return GetSqlValue(columnType, vOf.Elem().Interface())
	}
	//if columnType.Name() == "NESTING_EVENT_TYPE" {
	//	fmt.Println("NESTING_EVENT_TYPE value type", reflect.TypeOf(data).String(), " value is ", data)
	//}
	switch v := data.(type) {
	case sql.NullString:
		if !v.Valid {
			return nil
		}
		value = (v).String
	case sql.NullBool:
		if !v.Valid {
			return nil
		}
		value = (v).Bool
	case sql.NullByte:
		if !v.Valid {
			return nil
		}
		value = (v).Byte
	case sql.NullFloat64:
		if !v.Valid {
			return nil
		}
		value = (v).Float64
	case sql.NullInt16:
		if !v.Valid {
			return nil
		}
		value = (v).Int16
	case sql.NullInt32:
		if !v.Valid {
			return nil
		}
		value = (v).Int32
	case sql.NullInt64:
		if !v.Valid {
			return nil
		}
		value = (v).Int64
	case sql.NullTime:
		if !v.Valid {
			return nil
		}
		value = (v).Time
	case sql.RawBytes:
		value = string(v)
	case []uint8:
		value = string(v)
	case string, int, int8, int16, int32, int64, float32, float64, bool, uint, uint8, uint16, uint32, uint64:
		value = v
	case time.Time:
		value = v
	default:
		baseValue, isBaseType := dialect.GetBaseTypeValue(value)
		if isBaseType {
			value = baseValue
			return
		}
		value = v
		//panic("GetSqlValue data [" + fmt.Sprint(data) + "] data type [" + reflect.TypeOf(data).String() + "] name [" + columnType.Name() + "] databaseType [" + columnType.DatabaseTypeName() + "] not support")
	}
	return
}

func SetStructColumnValues(columnValueMap map[string]interface{}, strValue reflect.Value, dia dialect.Dialect) {
	if len(columnValueMap) == 0 {
		return
	}
	tOf := strValue.Type()

	_, structColumnMap := getStructColumn(tOf, dia)

	for columnName, columnValue := range columnValueMap {
		field, find := structColumnMap[columnName]
		/*if !find {
			field, find = structColumnMap[columnName]
		}*/
		if !find {
			continue
		}
		valueTypeOf := reflect.TypeOf(columnValue)
		columnValueType := ""
		fieldType := field.Type.String()
		if valueTypeOf != nil {
			columnValueType = valueTypeOf.String()
		}
		if columnValueType != fieldType {
			switch fieldType {
			case "string":
				columnValue = dialect.GetStringValue(columnValue)
			case "int8", "int16", "int32", "int64", "int":
				str := dialect.GetStringValue(columnValue)
				var num int64
				if str != "" {
					num, _ = dialect.StringToInt64(str)
				}
				if fieldType == "int8" {
					columnValue = int8(num)
				} else if fieldType == "int16" {
					columnValue = int16(num)
				} else if fieldType == "int32" {
					columnValue = int32(num)
				} else if fieldType == "int64" {
					columnValue = num
				} else if fieldType == "int" {
					columnValue = int(num)
				}
			case "float32", "float64":
				str := dialect.GetStringValue(columnValue)
				var num float64
				if str != "" {
					num, _ = strconv.ParseFloat(str, 64)
				}
				if fieldType == "float32" {
					columnValue = float32(num)
				} else if fieldType == "float64" {
					columnValue = num
				}
			case "time.Time":
				if columnValue == nil || columnValue == 0 {
					columnValue = time.Time{}
					break
				}
				valueOf := reflect.ValueOf(columnValue)
				if valueOf.Type().String() == "time.Time" {
					if t, ok := valueOf.Interface().(time.Time); ok {
						columnValue = t
					} else {
						columnValue = time.Time{}
					}

				}
			case "[]uint8":
				str := dialect.GetStringValue(columnValue)
				data := []byte(str)
				columnValue = data

			case "decimal.Decimal":
				str := dialect.GetStringValue(columnValue)

				dunm, err := decimal.NewFromString(str)
				if err != nil {
					columnValue = decimal.NewFromInt(0)
				} else {
					columnValue = dunm
				}

			case "*decimal.Decimal":
				str := dialect.GetStringValue(columnValue)
				dunm, err := decimal.NewFromString(str)
				if err != nil {
					dtemp := decimal.NewFromInt(0)
					columnValue = &dtemp
				} else {
					columnValue = &dunm
				}

			case "*string":
				str := dialect.GetStringValue(columnValue)
				columnValue = &str
			case "*float32", "*float64":

				str := dialect.GetStringValue(columnValue)
				var num float64
				if str != "" {
					num, _ = strconv.ParseFloat(str, 64)
				}
				if fieldType == "*float32" {
					f := float32(num)
					columnValue = &f
				} else if fieldType == "float64" {
					columnValue = &num
				}

			case "*int8", "*int16", "*int32", "*int64", "*int":
				str := dialect.GetStringValue(columnValue)
				var num int64
				if str != "" {
					num, _ = dialect.StringToInt64(str)
				}
				if fieldType == "*int8" {
					i := int8(num)
					columnValue = &i
				} else if fieldType == "*int16" {
					i := int16(num)
					columnValue = &i
				} else if fieldType == "*int32" {
					i := int32(num)
					columnValue = &i
				} else if fieldType == "*int64" {
					columnValue = &num
				} else if fieldType == "*int" {
					i := int(num)
					columnValue = &i
				}
			case "*time.Time":

				if columnValue == nil || columnValue == 0 {
					columnValue = &time.Time{}
					break
				}
				valueOf := reflect.ValueOf(columnValue)

				if valueOf.Type().String() == "time.Time" {
					if t, ok := valueOf.Interface().(time.Time); ok {
						columnValue = &t
					} else {
						columnValue = &time.Time{}
					}

				}

			}
		}
		valueOf := reflect.ValueOf(columnValue)
		strValue.FieldByName(field.Name).Set(valueOf)
	}
}

func getStructColumn(tOf reflect.Type, dia dialect.Dialect) (structFieldMap map[string]reflect.StructField, structColumnMap map[string]reflect.StructField) {

	structFieldMap = map[string]reflect.StructField{}
	structColumnMap = map[string]reflect.StructField{}
	for i := 0; i < tOf.NumField(); i++ {
		field := tOf.Field(i)
		structFieldMap[field.Name] = field
		str := field.Tag.Get("column")
		if str != "" && str != "-" {
			ss := strings.Split(str, ",")

			if dia.DialectType().Name == "shentong" || dia.DialectType().Name == "dm" {
				structColumnMap[strings.ToUpper(ss[0])] = field
				structColumnMap[strings.ToLower(ss[0])] = field
			} else {
				structColumnMap[ss[0]] = field
			}

		} else {
			str = field.Tag.Get("json")
			if str != "" && str != "-" {
				ss := strings.Split(str, ",")
				if dia.DialectType().Name == "shentong" || dia.DialectType().Name == "dm" {
					structColumnMap[strings.ToUpper(ss[0])] = field
					structColumnMap[strings.ToLower(ss[0])] = field
				} else {
					structColumnMap[ss[0]] = field
				}
			}
		}
	}
	return
}
